package consensus

import (
	"encoding/json"
	"fmt"
	"osiris/config"
	"osiris/core/conversion"
	"osiris/core/state"
	"osiris/core/txpool"
	"osiris/dao"
	"osiris/dto"
	"osiris/errors"
	"osiris/logger"
	"osiris/ocrypto/ecdsa"
	"time"

	//"github.com/davecgh/go-spew/spew"
	"github.com/libp2p/go-libp2p/core/crypto"
	peer "github.com/libp2p/go-libp2p/core/peer"
)

// POS 
type POS struct {
	ChainID int
	PeerID  peer.ID
}

func (pos POS) OnCreateBlock(blockJsonChan chan string) error {
	//等一个时隙开始构建区块
	logger.Info(map[string]interface{}{"[consensus] [pos.OnCreateBlock()] ": "start countdown"})
	time.Sleep(config.PosTimeSlot * time.Second)
	logger.Info(map[string]interface{}{"[consensus] [pos.OnCreateBlock()] ": "end countdown"})

	//构建区块
	blockDto, err1 := pos.buildBlock(pos.ChainID)
	if err1 != nil {
		logger.Error(map[string]interface{}{"[consensus] [pos.OnCreateBlock()] buid block": err1})

		//如果是没有记账者的错误，再等一个时隙重新构建区块
		if _, ok := err1.(*errors.NoAvailablePeerError); ok {
			return pos.OnCreateBlock(blockJsonChan)
		}

		blockJsonChan <- ""
		return err1
	}

	//blockDto转json
	json, err2 := json.Marshal(blockDto)
	if err2 != nil {
		logger.Error(map[string]interface{}{"[consensus] [pos.OnCreateBlock()] BlockDto marshal ": err2})
		blockJsonChan <- ""
		return err2
	}

	AddRecentHeader(blockDto.BlockHeader)

	blockJsonChan <- string(json)
	logger.Info(map[string]interface{}{"[consensus] [pos.OnCreateBlock()] peerID: " + pos.PeerID.String(): "successfully generate blockDto"})

	pos.onBlockVerified(blockDto)

	return nil
}

func (pos POS) OnReceiveBlock(blockDto dto.BlockDto, pubKey crypto.PubKey) error {
	//哈希、签名验证
	succeed := blockDto.Verify(pubKey)
	if !succeed {
		return &errors.BlockVerifyError{}
	}

	//处理区块
	err2 := pos.handleBlock(blockDto)
	if err2 != nil {
		logger.Error(map[string]interface{}{"[consensus] [pos.OnReceiveBlock()] pos.handleBlock() ": err2})
		return err2
	}

	logger.Info(map[string]interface{}{"[consensus] [pos.OnReceiveBlock()] ": "successfully handle blockDto"})

	pos.onBlockVerified(blockDto)

	return nil
}

func (pos POS) onBlockVerified(blockDto dto.BlockDto) {
	//成功创建区块后后清理交易池,并执行确认交易之后的事
	txpool.OnTxsConfirmed(blockDto.Txs)

	//将新区块加入区块链
	go dao.AddBlock(blockDto.BlockHeader)

	//存储交易
	go dao.AddTxs(blockDto.BlockHeader, blockDto.Txs)
}

func (pos POS) buildBlock(chainID int) (dto.BlockDto, error) {
	//发生错误将账户、节点状态树回滚到原来的状态
	accountRollbackChan := state.GetAccountRollbackChan(chainID)
	peerRollbackChan := state.GetPeerRollbackChan(chainID)
	errOccured := true
	defer func() {
		if errOccured {
			accountRollbackChan <- true
			peerRollbackChan <- true
		}
	}()

	//填充时间戳、链ID
	block := dto.BlockDto{
		BlockHeader: dto.BlockHeader{
			Timestamp: time.Now().Unix(),
			ChainID:   chainID,
		},
		WriterPeerID: pos.PeerID.String(),
	}

	//填充前一个区块的区块hash、区块高度
	preHeader := dao.GetCurrentHeader()
	block.BlockHeader.Height = preHeader.Height + 1
	block.BlockHeader.PreBlockHash = preHeader.HeaderHash

	//查看有无节点可以结束冷却（代币已全部以release交易的形式返回给账户）
	err1 := state.WarmUpPeers(chainID)
	if err1 != nil {
		logger.Error(map[string]interface{}{"[consensus] [build block]state.WarmUpPeers()": err1})
		return block, err1
	}

	//交易打包
	rawTxs := txpool.Pack()
	//附加release和reward交易（解押代币，执行出块奖励）
	txs := pos.processingTxs(rawTxs)
	//执行交易
	sucessTxs := state.CommitTxBatch(txs)
	logger.Info(map[string]interface{}{"[consensus] [build Block] Commit TX Batch": fmt.Sprintf("commit txs: %d, succeed: %d", len(rawTxs), len(sucessTxs))})

	//自身节点进入冷却
	peerIDBytes, err2 := pos.PeerID.MarshalBinary()
	if err2 != nil {
		logger.Error(map[string]interface{}{"[consensus] [build block] pos.PeerID.MarshalBinary()": err2})
		return block, err2
	}
	
	err3 := state.CoolDownPeer(pos.ChainID, peerIDBytes)
	if err3 != nil {
		logger.Error(map[string]interface{}{"[consensus] [build block]state.CoolDownPeer()": err3})
		return block, err3
	}
	

	//构建交易树
	txTrie := TxTrie{}
	err4 := txTrie.Build(sucessTxs)
	if err4 != nil {
		logger.Error(map[string]interface{}{"[consensus] [build block] Build tx trie": err4})
		return block, err4
	}
	
	//填充交易树根
	block.BlockHeader.TxHash = ecdsa.Keccak256HashBytesToStr(txTrie.trie.Hash())
	//填充交易列表
	block.Txs = sucessTxs
	

	//填充账户状态树根
	var err5 error
	block.BlockHeader.AccountStateHash, err5 = state.GetAccountTrieRoot(chainID)
	if err5 != nil {
		logger.Error(map[string]interface{}{"[consensus] [build Block] state.GetAccountTrieRoot()": err5})
		return block, err5
	}

	/* strA := []string{"12D3KooWC3CoFkgusyw2PsH1mr5NZvdsRQmqcUwAV1naKYhDuwrT", "12D3KooWQ9tv1hnW4JSSPFyMwceEE78vQE7JzqdZCw9DS9LGRT7j", "12D3KooWJAqRVZsMPoXzfjnDEZWixerkH47gwdscbRjv3UPVWETk", "12D3KooWKd5BHMyToRwAMDtecD1WojZwFyVrrVsHmWTNLm8ViCQs"}
	for _, peerID := range strA {
		peerIDBytes, _ := conversion.StrToPeerIDBytes(peerID)
		leaf, exist := state.GetPeerLeaf(peerIDBytes, 0)
		fmt.Println(peerID, exist)
		spew.Dump(leaf)
	} */

	//填充节点状态树的根
	var err6 error
	block.BlockHeader.PeerStateHash, err6 = state.GetPeerTrieRoot(chainID)
	if err6 != nil {
		logger.Error(map[string]interface{}{"[consensus] [build Block] state.GetPeerTrieRoot()": err6})
		return block, err6
	}

	//填充下一个记账节点的peer.ID.String()
	nextPeerID, err7 := state.GetWriterPeer(chainID)
	if err7 != nil {
		logger.Error(map[string]interface{}{"[consensus] [build Block] state.GetWriterPeer()": err7})
		return block, err7
	} else {
		logger.Info(map[string]interface{}{"[consensus] [build Block] state.GetWriterPeer()": fmt.Sprintf("next peer: %s", nextPeerID.String())})
	}
	block.BlockHeader.NextPeer = nextPeerID.String()

	//算hash、签名
	block.HashAndSign()
	errOccured = false
	return block, nil
}

func (pos POS) handleBlock(blockDto dto.BlockDto) error {
	//发生错误将账户、节点状态树回滚到原来的状态
	accountRollbackChan := state.GetAccountRollbackChan(blockDto.ChainID)
	peerRollbackChan := state.GetPeerRollbackChan(blockDto.ChainID)
	errOccured := true
	defer func() {
		if errOccured {
			accountRollbackChan <- true
			peerRollbackChan <- true
		}
	}()

	preHeader := dao.GetCurrentHeader()
	//验证时间戳
	if preHeader.Timestamp >= blockDto.Timestamp {
		err := &errors.BlockVerifyError{
			Msg: "invalid timestamp",
		}
		logger.Warn(map[string]interface{}{"[consensus] [handle Block]": err})
		return err
	}

	//验证区块高度
	if preHeader.Height != 0 && preHeader.Height+1 != blockDto.Height {
		err := &errors.BlockVerifyError{
			Msg: "invalid height",
		}
		logger.Warn(map[string]interface{}{"[consensus] [handle Block]": err})
		return err
	}

	//验证前一个区块的hash
	if preHeader.Height != 0 && preHeader.HeaderHash != blockDto.PreBlockHash {
		err := &errors.BlockVerifyError{
			Msg: "invalid previous block hash",
		}
		logger.Warn(map[string]interface{}{"[consensus] [handle Block]": err})
		return err
	}

	//查看有无节点可以结束冷却（代币已全部以release交易的形式返回给账户）
	err1 := state.WarmUpPeers(blockDto.ChainID)
	if err1 != nil {
		logger.Error(map[string]interface{}{"[consensus] [build block]state.WarmUpPeers()": err1})
		return err1
	}

	//执行交易,发生错误回滚至所有交易执行前的状态，并拒绝该区块
	txs := blockDto.Txs
	err2 := state.TryCommitTxBatch(blockDto.ChainID, txs)
	if err2 != nil {
		logger.Warn(map[string]interface{}{"[consensus] [handle Block] state.TryCommitTxBatch()": err2})
		return err2
	}

	//冷却记账节点
	peerIDBytes, err3 := conversion.StrToPeerIDBytes(blockDto.WriterPeerID)
	if err3 != nil {
		logger.Error(map[string]interface{}{"[consensus] [handle block] conversion.StrToPeerIDBytes": err3})
		return err3
	}
	err4 := state.CoolDownPeer(pos.ChainID, peerIDBytes)
	if err4 != nil {
		logger.Error(map[string]interface{}{"[consensus] [handle block]state.CoolDownPeer()": err4})
		return err4
	}

	//验证账户状态树根
	accountTrieRoot, err5 := state.GetAccountTrieRoot(blockDto.ChainID)
	if err5 != nil {
		logger.Error(map[string]interface{}{"[consensus] [handle Block] dstate.GetAccountTrieRoot()": err5})
		return err5
	}
	if blockDto.AccountStateHash != accountTrieRoot {
		err := &errors.BlockVerifyError{
			Msg: "invalid account trie root hash",
		}
		logger.Warn(map[string]interface{}{"[consensus] [handle Block]": err})
		return err
	}

	/* strA := []string{"12D3KooWC3CoFkgusyw2PsH1mr5NZvdsRQmqcUwAV1naKYhDuwrT", "12D3KooWQ9tv1hnW4JSSPFyMwceEE78vQE7JzqdZCw9DS9LGRT7j", "12D3KooWJAqRVZsMPoXzfjnDEZWixerkH47gwdscbRjv3UPVWETk", "12D3KooWKd5BHMyToRwAMDtecD1WojZwFyVrrVsHmWTNLm8ViCQs"}
	for _, peerID := range strA {
		peerIDBytes, _ := conversion.StrToPeerIDBytes(peerID)
		leaf, exist := state.GetPeerLeaf(peerIDBytes, 0)
		fmt.Println(peerID, exist)
		spew.Dump(leaf)
	} */
	//验证节点状态树根
	peerTrieRoot, err6 := state.GetPeerTrieRoot(blockDto.ChainID)
	if err4 != nil {
		logger.Error(map[string]interface{}{"[consensus] [handle Block] dstate.GetPeerTrieRoot()": err6})
		return err6
	}
	if blockDto.PeerStateHash != peerTrieRoot {
		err := &errors.BlockVerifyError{
			Msg: "invalid peer trie root hash",
		}
		logger.Warn(map[string]interface{}{"[consensus] [handle Block]": err})
		return err
	}

	//TODO: conform:有没必要验证交易树根，是不是状态树根都一样就行了？
	logger.Info(map[string]interface{}{fmt.Sprintf("[consensus] [handle Block] height: %d, blockHash: %s", blockDto.Height, blockDto.HeaderHash): "success"})
	return nil
}

func (pos POS) processingTxs(rawTxs []dto.TxData) []dto.TxData {
	txs := make([]dto.TxData, 0)
	//打包期间拒绝执行向自身节点发起的pledge交易
	for _, tx := range rawTxs {
		if tx.TxType == dto.TX_PLEDGE && tx.ToAddr == pos.PeerID.String() {
			continue
		}
		txs = append(txs, tx)
	}

	confirmedPledgeTxs := txpool.FlushConfirmedPledgeTxs()
	if len(confirmedPledgeTxs) == 0 {
		return txs
	}

	ts := time.Now().Unix()
	var totalAmount int64 = 0
	pledgeAmounts := make(map[string]int64, len(confirmedPledgeTxs))
	//解押代币（TX_RELEASE)
	for addrStr, pledgeTxs := range confirmedPledgeTxs {
		for _, pledgeTx := range pledgeTxs {
			totalAmount += pledgeTx.Amount
			pledgeAmount, exist := pledgeAmounts[addrStr]
			if exist {
				pledgeAmounts[addrStr] = pledgeAmount + pledgeTx.Amount
			} else {
				pledgeAmounts[addrStr] = pledgeTx.Amount
			}

			releaseTx := dto.TxData{
				Timestamp: ts,
				ChainID:   pos.ChainID,
				TxType:    dto.TX_RELEASE,
				FromAddr:  pos.PeerID.String(),
				ToAddr:    addrStr,
				Amount:    pledgeTx.Amount,
				CoinSince: pledgeTx.CoinSince,
			}
			releaseTx.HashAndSign(nil, false)
			txs = append(txs, releaseTx)
		}
	}

	//出块奖励(TX_REWARD)
	for addrStr, pledgeAmount := range pledgeAmounts {
		rewardAmount := (int64)(pledgeAmount / 10)
		if rewardAmount == 0 {
			rewardAmount = 1
		}

		rewardTx := dto.TxData{
			Timestamp: ts,
			ChainID:   pos.ChainID,
			TxType:    dto.TX_REWARD,
			FromAddr:  pos.PeerID.String(),
			ToAddr:    addrStr,
			Amount:    rewardAmount,
			CoinSince: ts,
		}
		rewardTx.HashAndSign(nil, false)
		txs = append(txs, rewardTx)
	}

	return txs
}
